const flow = require('./flow')
const throwNewError = require('./throwError')
const path = require('path');
const fs = require('fs');
const mammoth = require('mammoth');
const pdfParse = require('pdf-parse');
const WordExtractor = require('word-extractor');
const { createCanvas, loadImage, Image } = require('canvas');
const crypto = require('crypto');
const JSZip = require('jszip');
const config = require('../../config');
const getMimeType = async (stream) => {
    const { getMimeType } = await import('stream-mime-type');
    return getMimeType(stream);
};
const openai = async (stream) => {
    const { OpenAI } = await import('openai');
    return OpenAI;
};
const { PDFDocument, skewRadians } = require('pdf-lib');

const { PDFExtract } = require('pdf.js-extract');
const officeTextExtractor = async () => {
    return await import('office-text-extractor')
}

const extractImagesFromPdf = async (buffer) => {
    let re = await import('pdf-extract-image')
    console.log(re)
    return await re.extractImagesFromPdf(new Uint8Array(buffer))
}
const cheerio = require('cheerio');

const { parseOfficeAsync } = require('officeparser')
const pdfjsLib = require('pdfjs-dist');

module.exports = {
    methods: {
        user: () => require('../methods/user'),
        settings: () => require('../methods/settings'),
        category: () => require('../methods/category'),
        paper: () => require('../methods/paper'),
        question: () => require('../methods/question'),
        exam: () => require('../methods/exam'),
        chat: () => require('../methods/chat'),
    },
    dbf: () => require('../db'),
    array: {
        delete: {
            byIndex(arr = [], index = -1) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                if (check.isInt(index) == false) {
                    check.throwError('index应为整数')
                }
                return [...arr.slice(0, index), ...arr.slice(index + 1)]
            },
            byKeyValue(arr = [], key = '', value = undefined) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                return arr.filter(item => item[key] != value)
            },
            byValue(arr = [], value = undefined) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                return arr.filter(item => item != value)
            },
            clearUndefined(arr = []) {
                if (check.isArray(arr) == false) {
                    check.throwError('arr应为数组')
                }
                return arr.filter(item => item != undefined)
            }
        },
        async format(arr = [], func = async (e) => { return e }) {
            if (check.isArray(arr) == false) {
                check.throwError('arr应为数组')
            }
            for (let i = 0; i < arr.length; i++) {
                arr[i] = await func(arr[i])
            }
            return arr
        },
        count(arr, value) {
            let re = 0
            for (let i = 0; i < arr.length; i++) {
                if (arr[i] === value) re++
            }
            return re
        },
        getFirstIndex: {
            byKeyValue(arr = [], key = '', value = undefined) {
                for (let i = 0; i < arr.length; i++) {
                    if (arr[i][key] === value) return i
                }
                return -1
            }
        }
    },
    object: {
        async for(obj = {}, func = async (key, value) => { return value }) {
            for (let i = 0; i < Object.keys(obj).length; i++) {
                obj[Object.keys(obj)[i]] = await func(Object.keys(obj)[i], obj[Object.keys(obj)[i]])
            }
            return obj
        }
    },
    string: {
        delete: {
            bySubString(string, subString) {
                string = string.split(subString)
                let re = ''
                for (let i = 0; i < string.length; i++) {
                    re += string[i]
                }
                return re
            }
        }
    },
    JSON: {
        parse(string) {
            try {
                return JSON.parse(string)
            } catch (error) {
                return string
            }
        },
        stringify(obj) {
            return JSON.stringify(obj)
        }
    },
    deepCopy(o) {
        return this.JSON.parse(this.JSON.stringify(o))
    },
    sliceByPageSize(arr, page, size) {
        return arr.slice((page - 1) * size, page * size)
    },
    encodeString(string) {

        function isLetter(code) {
            if (code >= 65 && code <= 90) {
                return 1
            }
            if (code >= 97 && code <= 122) {
                return 1
            }
            if (code > 90 && code < 97) {
                return 2
            }
            if (code < 65) {
                return 0
            }
            return 2
        }

        function getNumberString(n, x) {
            n = JSON.stringify(n)
            for (let i = n.length; i < x; i++) {
                n = '0' + n
            }
            return n
        }

        string = string.split('')
        let string1 = ''
        for (let i = 0; i < string.length; i++) {
            string1 += JSON.stringify(JSON.stringify(string[i].charCodeAt(0)).length) + JSON.stringify(string[i].charCodeAt(0))
        }
        string1 = string1.split('')
        for (let i = 0; i < string1.length; i++) {
            if (string1[i] == '0') {
                continue
            }
            let c = i
            let ss = string1[c]
            while (true) {
                let cd = isLetter(parseInt(ss))
                if (cd == 2) {
                    break
                }
                if (cd == 1) {
                    string1[c] = String.fromCharCode(parseInt(ss))
                    for (let i1 = i; i1 < c; i1++) {
                        string1[i1] = '-'
                    }
                    i = c
                    break
                }
                c++
                if (c == string1.length) {
                    break
                }
                ss += string1[c]
            }
        }
        for (let i = 0; i < string1.length - 1; i++) {
            if (string1[i] == '2' && string1[i + 1] == '2') {
                string1[i] = '?'
                string1[i + 1] = '-'
            }
        }
        let string2 = []
        for (let i = 0; i < string1.length; i++) {
            if (string1[i] == '-') {
                continue
            }
            string2.push(JSON.stringify((string1[i].charCodeAt(0) - string1[i].charCodeAt(0) % 52) / 52) + getNumberString(string1[i].charCodeAt(0) % 52, 2))
        }
        let string3 = ''
        for (let i = string2.length - 1; i >= 0; i--) {
            string3 += string2[i]
        }
        string3 = string3.split('')
        let string4 = []
        for (let i = 0; i < string3.length; i += 2) {
            let ch = string3[i]
            if (i + 1 != string3.length) {
                ch += string3[i + 1]
            }
            if (parseInt(ch) < 52 && ch.length == 2) {
                if (parseInt(ch) < 26) {
                    string4.push(String.fromCharCode(parseInt(ch) + 65))
                } else {
                    string4.push(String.fromCharCode(parseInt(ch) + 97 - 26))
                }
            } else {
                string4.push(ch)
            }
        }
        let string5 = ''
        for (let i = 0; i < string4.length; i++) {
            string5 += string4[i]
        }
        return string5
    },
    decodeString(string) {
        function getNumberString(n, x) {
            n = JSON.stringify(n)
            for (let i = n.length; i < x; i++) {
                n = '0' + n
            }
            return n
        }

        string = string.split('')
        for (let i = 0; i < string.length; i++) {
            if (string[i].charCodeAt(0) >= 48 && string[i].charCodeAt(0) <= 56) {
                continue
            }
            if (string[i].charCodeAt(0) <= 90) {
                string[i] = getNumberString(string[i].charCodeAt(0) - 65, 2)
            } else {
                string[i] = getNumberString(string[i].charCodeAt(0) - 97 + 26, 2)
            }
        }
        let string2 = ''
        for (let i = 0; i < string.length; i++) {
            if (string[i] == '-8') {
                string2 += '9'
                continue
            }
            string2 += string[i]
        }
        string2 = string2.split('')
        let string3 = []
        for (let i = 0; i < string2.length; i += 3) {
            string3.push(getNumberString(parseInt(string2[i]) * 52 + parseInt(string2[i + 1] + string2[i + 2]), 3))
        }
        let string4 = []
        for (let i = string3.length - 1; i >= 0; i--) {
            string4.push(String.fromCharCode(parseInt(string3[i])))
        }
        let string5 = ''
        for (let i = 0; i < string4.length; i++) {
            if (string4[i] == '?') {
                string5 += '22'
                continue
            }
            if ((string4[i].charCodeAt(0) >= 48 && string4[i].charCodeAt(0) <= 57) == false) {
                string5 += JSON.stringify(string4[i].charCodeAt(0))
                continue
            }
            string5 += string4[i]
        }
        string5 = string5.split('')
        let string6 = ''
        for (let i = 0; i < string5.length; i++) {
            let n = parseInt(string5[i])
            let s = ''
            for (let i1 = 0; i1 < n; i1++) {
                s += string5[i + 1 + i1]
            }
            string6 += String.fromCharCode(parseInt(s))
            i += n
        }
        return string6
    },
    formatDate2(date) {
        const year = date.getFullYear();
        const month = (date.getMonth() + 1).toString().padStart(2, '0');  // 月份从0开始，+1调整
        const day = date.getDate().toString().padStart(2, '0');
        const hours = date.getHours().toString().padStart(2, '0');
        const minutes = date.getMinutes().toString().padStart(2, '0');
        const seconds = date.getSeconds().toString().padStart(2, '0');

        return `${year}${month}${day}${hours}${minutes}${seconds}`;
    },
    formatDate3(date) {
        const year = date.getFullYear();
        const month = (date.getMonth() + 1).toString().padStart(2, '0');  // 月份从0开始，+1调整
        const day = date.getDate().toString().padStart(2, '0');
        const hours = date.getHours().toString().padStart(2, '0');
        const minutes = date.getMinutes().toString().padStart(2, '0');
        const seconds = date.getSeconds().toString().padStart(2, '0');

        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    formatTimeNew(inputTime = new Date()) {
        function getPeriod(hours) {
            if (hours >= 0 && hours < 6) {
                return '凌晨';
            } else if (hours >= 6 && hours < 11) {
                return '上午';
            } else if (hours >= 11 && hours < 14) {
                return '中午';
            } else if (hours >= 14 && hours < 18) {
                return '下午';
            } else {
                return '晚上';
            }
        }

        function getWeekName(n) {
            switch (n) {
                case 0:
                    return '星期天'
                case 1:
                    return '星期一'
                case 2:
                    return '星期二'
                case 3:
                    return '星期三'
                case 4:
                    return '星期四'
                case 5:
                    return '星期五'
                case 6:
                    return '星期六'
            }
        }
        let currentTime = new Date()
        let oneDaySecond = 86400000
        let currentDayStart = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000)
        let lastDayStart = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000 - oneDaySecond)
        let nextDayStart = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000 + oneDaySecond)
        let nextDayEnd = new Date(currentTime.getTime() - currentTime.getHours() * 60 * 60 * 1000 - currentTime
            .getMinutes() * 60 * 1000 - currentTime.getSeconds() * 1000 + oneDaySecond * 2)
        let prefix = ''
        if (inputTime >= currentDayStart && inputTime < nextDayStart) {
            prefix = '今天'
        }
        if (inputTime >= lastDayStart && inputTime < currentDayStart) {
            prefix = '昨天'
        }
        if (inputTime >= nextDayStart && inputTime < nextDayEnd) {
            prefix = '明天'
        }
        if (prefix == '') {
            prefix = (inputTime.getFullYear() == currentTime.getFullYear() ? '' : inputTime.getFullYear().toString() +
                '年') + (inputTime.getMonth() + 1).toString() + '月' + inputTime.getDate() + '日 '
        }
        prefix += getPeriod(inputTime.getHours()) + ' '
        prefix += inputTime.getHours().toString() + ':'
        if (inputTime.getMinutes() < 10) {
            prefix += '0'
        }
        prefix += inputTime.getMinutes().toString() + ' '
        prefix += getWeekName(inputTime.getDay())
        return prefix
    },
    wait(n) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                return resolve()
            }, n * 1000);
        })
    },
    flow,
    throwNewError,
    async deleteFile(filename) {
        const filePath = path.join(__dirname, '../../files', filename)
        try {
            const fileExists = await new Promise((resolve, reject) => {
                fs.access(filePath, fs.constants.F_OK, (err) => {
                    if (err) {
                        reject(err)
                    } else {
                        resolve(true)
                    }
                })
            })
            if (fileExists) {
                await new Promise((resolve, reject) => {
                    fs.unlink(filePath, (err) => {
                        if (err) {
                            reject(err);
                        } else {
                            resolve();
                        }
                    });
                });
                console.log(`文件 ${filename} 已被删除`);
            } else {
                console.log(`文件 ${filename} 不存在`);
            }
        } catch (error) {
            console.error(`删除文件时出错: ${error}`);
        }
    },
    getRandomString() {
        let re = ''
        for (let i = 0; i < 20; i++) {
            re += `${parseInt(Math.random() * 10)}`
        }
        return re
    },
    sortByPinyin(words) {
        const pinyin = require("pinyin-pro");
        return words.sort((a, b) => {
            const pinyinA = pinyin(a, { style: pinyin.STYLE_NORMAL }).join("");
            const pinyinB = pinyin(b, { style: pinyin.STYLE_NORMAL }).join("");
            return pinyinA.localeCompare(pinyinB);
        });
    },
    getHTMLAttributes(htmlString, tagName, attributeName) {
        const cheerio = require('cheerio')
        const $ = cheerio.load(htmlString);

        return $(tagName)
            .map((i, element) => $(element).attr(attributeName))
            .get();
    },
    replaceHTMLAttributeValue(htmlString, attributeName, oldValue, newValue) {
        const cheerio = require('cheerio');
        const $ = cheerio.load(htmlString);

        // 遍历所有包含指定属性的元素
        $('[' + attributeName + ']').each((i, element) => {
            // 获取当前属性的值
            const currentValue = $(element).attr(attributeName);

            // 如果属性值匹配旧值，替换为新值
            if (currentValue === oldValue) {
                $(element).attr(attributeName, newValue);
            }
        });

        // 选择主体部分（去掉 <html>, <head>, <body> 标签）并获取内容
        const resultHtml = $('body').html() || $.html(); // 获取 <body> 内部的 HTML，如果没有 <body> 标签则返回完整 HTML

        console.log(resultHtml); // 打印修改后的 HTML 用于调试

        // 返回修改后的 HTML 字符串
        return resultHtml;
    },
    getFileContent(fileName) {
        return new Promise((resolve, reject) => {
            const filesFolder = path.join(__dirname, '../../files');
            // 构建文件路径
            const filePath = path.join(filesFolder, fileName);

            // 检查文件是否存在
            if (!fs.existsSync(filePath)) {
                return reject('File not found');
            }

            // 读取文件内容
            fs.readFile(filePath, 'utf8', (err, data) => {
                if (err) {
                    return reject('Error reading file');
                }
                resolve(data); // 返回文件内容
            });
        })
    },
    getSinglePrice(item, options, num) {
        let re = item.price
        for (let i = 0; i < Object.keys(options).length; i++) {
            let key = Object.keys(options)[i]
            let value = options[key]
            if (!item.options) continue
            for (let i1 = 0; i1 < item.options.length; i1++) {
                if (item.options[i1].title == key) {
                    if (item.options[i1].items.indexOf(value) != -1) {
                        let ind = item.options[i1].items.indexOf(value)
                        re += item.options[i1].prices[ind]
                    }
                }
            }
        }
        return re * num
    },
    formatNumToStringWithLength(num, length, char = '0') {
        return num.toString().padStart(length, char);
    },
    getPriceDisplay(price) {
        return (price / 100).toString() + ((price / 100).toString().split('.')[1] ? ((price / 100).toString().split('.')[1].length == 1 ? '0' : '') : '')
    },
    parseXML(xml) {
        return new Promise((resolve, reject) => {
            const xml2js = require('xml2js')
            xml2js.parseString(xml, { trim: true, explicitArray: false }, (err, result) => {
                if (err) {
                    resolve(undefined)
                } else {
                    resolve(result.xml)
                }
            })
        })
    },
    getStatusName(status) {
        if (status == 0) return '待支付'
        if (status == 1) return '正在备餐'
        if (status == 2) return '正在配送'
        if (status == 3) return '等待取餐'
        if (status == 4) return '已完成'
    },
    getNowSQL() {
        return this.formatDate3(new Date())
    },
    serverTime() {
        const now = new Date();
        const utcOffset = now.getTimezoneOffset(); // 获取服务器的 UTC 偏移，单位为分钟
        const chinaOffset = 8 * 60; // 中国 UTC+8，单位为分钟
        const chinaTime = new Date(now.getTime() + (chinaOffset - utcOffset) * 60000); // 调整时间
        return chinaTime.toString();
    },
    async generateQRCode(path, width = 430) {
        const axios = require('axios')
        const url = `https://api.weixin.qq.com/wxa/getwxacode?access_token=${await app.getAccessToken()}`;
        const response = await axios.post(url, {
            path,
            width
        }, { responseType: 'arraybuffer' });

        // 将响应数据转换为 base64 字符串，并加上头部信息
        const base64QRCode = 'data:image/png;base64,' + Buffer.from(response.data, 'binary').toString('base64');
        return base64QRCode;
    },
    async readDocumentText(filePath) {
        const ext = path.extname(filePath).toLowerCase();

        if (ext === '.docx') {
            // 处理 .docx 文件
            try {
                const result = await mammoth.extractRawText({ path: filePath });
                return result.value;
            } catch (err) {
                throw new Error(`读取 .docx 文件失败: ${err.message}`);
            }
        } else if (ext === '.doc') {
            // 处理 .doc 文件
            try {
                const extractor = new WordExtractor();
                const doc = await extractor.extract(filePath);
                return doc.getBody();
            } catch (err) {
                throw new Error(`读取 .doc 文件失败: ${err.message}`);
            }
        } else if (ext === '.pdf') {
            // 处理 .pdf 文件
            try {
                const dataBuffer = fs.readFileSync(filePath);
                const data = await pdfParse(dataBuffer);
                return data.text;
            } catch (err) {
                throw new Error(`读取 PDF 文件失败: ${err.message}`);
            }
        } else {
            throw new Error(`不支持的文件类型: ${ext}`);
        }
    },
    parseNumberedSections(input = '') {
        const result = {};

        let splits = [
            {
                c: '.',
                num: input.split('.').length - 1
            },
            {
                c: '、',
                num: input.split('、').length - 1
            },
        ]
        splits = splits.sort((a, b) => {
            return b.num - a.num
        })
        let split = splits[0].c

        let current = 1
        while (1) {
            let str = `${current}${split}`
            let index = input.indexOf(str)
            if (index === -1) break
            input = input.slice(index + str.length)

            let nextStr = `${current + 1}${split}`
            let nextIndex = input.indexOf(nextStr)

            result[current] = (() => {
                if (nextIndex === -1) {
                    return input
                } else {
                    return input.slice(0, nextIndex)
                }
            })()

            current++
        }

        return result;
    },
    parseSingleQuestion(input = '', index) {
        index = parseInt(index)
        let re = {
            stems: '',
            options: {},
            analysis: '',
            type: '选择',
            img: '',
            optionTypes: {
                'A': 'multipleText', 'B': 'multipleText', 'C': 'multipleText', 'D': 'multipleText'
            }
        }

        let splits = [
            {
                c: '.',
                num: input.split('.').length - 1
            },
            {
                c: '、',
                num: input.split('、').length - 1
            },
        ]
        splits = splits.sort((a, b) => {
            return b.num - a.num
        })
        let split = splits[0].c

        let checks = [`A${split}`, `B${split}`, `C${split}`, `D${split}`, '解析']
        for (let i = 0; i < checks.length; i++) {
            let index = (() => {
                if (checks[i] === '解析') {
                    let in1 = input.indexOf('解析')
                    let in2 = input.indexOf('解答')
                    if (in1 === -1 && in2 === -1) return -1
                    if (in1 === -1) return in2
                    return in1
                } else {
                    return input.indexOf(checks[i])
                }
            })()
            if (index > -1) {
                checks[i] = {
                    item: checks[i],
                    index: index
                }
            }
        }
        checks = checks.filter(item => typeof (item) !== 'string').sort((a, b) => {
            return a.index - b.index
        })
        if (checks.length) {
            re.stems = input.slice(0, checks[0].index)
            for (let i = 0; i < checks.length; i++) {
                let str
                if (i === checks.length - 1) {
                    str = input.slice(checks[i].index + checks[i].item.length)
                } else {
                    str = input.slice(checks[i].index + checks[i].item.length, checks[i + 1].index)
                }

                str = str.replaceAll('】', '')
                str = str.replaceAll('【', '')
                str = str.trimStart()
                str = str.trimEnd()

                if (checks[i].item === '解析') {
                    re.analysis = str
                } else {
                    let tihao = `${index}.`
                    if (str.indexOf(tihao) !== 0) str = str.split(tihao)[0]
                    re.options[checks[i].item.replaceAll('.', '').replaceAll('、', '')] = str
                }
            }
        } else {
            re.stems = input
        }
        return re
    },
    removeLeadingSpaces(str) {
        return str.trimStart();
    },
    removeTrailingSpaces(str) {
        return str.trimEnd();
    },

    async readFileContent(fileName) {
        try {
            const filePath = path.join(__dirname, "../../files", fileName); // 计算文件路径
            const content = await new Promise((resolve, reject) => {
                fs.readFile(filePath, { encoding: "utf-8" }, (err, data) => {
                    if (err) {
                        reject(err)
                    } else {
                        resolve(data)
                    }
                })
            }) // 正确设置 encoding
            return content;
        } catch (error) {
            console.error("读取文件时出错:", error);
            return null; // 返回 null 以进行错误处理
        }
    },
    async extractImages(file) {
        const name = file.name.toLowerCase();
        if (name.endsWith('.docx')) {
            return extractFromDocx(file);
        } else if (name.endsWith('.doc')) {
            return extractFromDoc(file);
        } else if (name.endsWith('.pdf')) {
            return extractFromPdf(file);
        } else {
            throw new Error('Unsupported file type: ' + file.name);
        }
    },
    async createFileLikeObject(filePath) {
        function getMimeType(fileName) {
            const ext = path.extname(fileName).toLowerCase();
            switch (ext) {
                case '.jpg':
                case '.jpeg':
                    return 'image/jpeg';
                case '.png':
                    return 'image/png';
                case '.gif':
                    return 'image/gif';
                case '.pdf':
                    return 'application/pdf';
                case '.doc':
                    return 'application/msword';
                case '.docx':
                    return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
                default:
                    return 'application/octet-stream';
            }
        }
        const absolutePath = path.resolve(filePath);
        const data = await fs.promises.readFile(absolutePath);
        const stats = await fs.promises.stat(absolutePath);
        const fileName = path.basename(absolutePath);
        const mimeType = getMimeType(fileName);

        return {
            name: fileName,
            size: stats.size,
            lastModified: stats.mtimeMs,
            type: mimeType,
            buffer: data,
        };
    },
    async extractImagesFromFile(filePath) {
        const ext = path.extname(filePath).toLowerCase();
        const buffer = await new Promise((resolve, reject) => {
            fs.readFile(filePath, (err, data) => {
                if (err) {
                    reject(err)
                } else {
                    resolve(data)
                }
            })
        });

        // DOCX: 解压 ZIP，读取 word/media 下的文件
        if (ext === '.docx') {
            const zip = await JSZip.loadAsync(buffer);
            const media = zip.folder('word/media');
            if (!media) return [];
            return Promise.all(
                Object.values(media.files).map(async entry => {
                    const data = await entry.async('base64');
                    const suffix = path.extname(entry.name).slice(1);
                    return `data:image/${suffix};base64,${data}`;
                })
            );
        }

        // DOC: 转 HTML 并提取 <img> 标签的 src
        if (ext === '.doc') {
            const { value: html } = await mammoth.convertToHtml({ buffer });
            const imgSrcs = [];
            const imgRegex = /<img [^>]*src=["']([^"']+)["']/g;
            let m;
            while ((m = imgRegex.exec(html))) {
                imgSrcs.push(m[1]);
            }
            return imgSrcs;
        }

        // PDF: 遍历每页的 Resources.XObject，提取 Subtype=Image 的对象
        if (ext === '.pdf') {
            const { convert } = require('pdf-poppler');
            const tmpPath = path.join(__dirname, 'temp.pdf');
            fs.writeFileSync(tmpPath, buffer);

            const outputDir = path.join(__dirname, 'output');
            if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);

            const options = {
                format: 'jpeg',
                out_dir: outputDir,
                out_prefix: 'page',
                page: null // 全部页面
            };

            await convert(tmpPath, options);

            // 返回图像文件路径（或转成 base64）
            const imageFiles = fs.readdirSync(outputDir)
                .filter(file => file.endsWith('.jpg'))
                .map(file => path.join(outputDir, file));

            return imageFiles;
        }

        throw new Error(`不支持的文件类型：${ext}`);
    },
    openai: {
        send(content, type = 'text', imageName = '', streaming = false, onData = () => { }) {
            console.log('已发送', content)
            return new Promise(async (resolve, reject) => {
                try {
                    const opai = new (await openai())({
                        apiKey: config.openaiKey,
                        baseURL: config.openaiBaseURL,
                    });

                    const messages = [
                        {
                            role: "user",
                            content: (() => {
                                if (type === 'text') {
                                    return content;
                                }
                                return [
                                    {
                                        "type": "image_url",
                                        "image_url": {
                                            "url": `${config.serverURL}/api/file/download/${imageName}`
                                        },
                                    },
                                    { "type": "text", "text": content },
                                ];
                            })()
                        },
                    ];

                    if (streaming) {
                        // 采用流式返回
                        const completion = await opai.chat.completions.create({
                            model: type === 'text' ? config.textModelName : config.imageModelName,
                            messages: messages,
                            stream: true
                        });

                        for await (const chunk of completion) {
                            const chunkContent = chunk.choices[0].delta.content || '';
                            if (onData) {
                                onData(chunkContent); // 触发回调函数
                            }
                        }

                        resolve();
                    } else {
                        // 非流式返回
                        const completion = await opai.chat.completions.create({
                            model: type === 'text' ? config.textModelName : config.imageModelName,
                            messages: messages,
                        });

                        console.log(completion.choices[0].message.content)
                        resolve(completion.choices[0].message.content);
                    }
                } catch (error) {
                    reject(error);
                }
            });
        }
    },
    async mergeLabeledImages(questionBase64, opt1Base64, opt2Base64, opt3Base64, opt4Base64) {
        const inputs = [
            { base64: `` }, // 题目标签图
            { base64: questionBase64 },

            { base64: `` }, // 选项1标签图
            { base64: opt1Base64 },

            { base64: `` }, // 选项2标签图
            { base64: opt2Base64 },

            { base64: `` }, // 选项3标签图
            { base64: opt3Base64 },

            { base64: `` }, // 选项4标签图
            { base64: opt4Base64 },
        ].filter(item => item.base64); // 去掉无效的 base64
        const results = await Promise.allSettled(
            inputs.map(item => loadImage(item.base64))
        );

        const images = results
            .filter(result => result.status === 'fulfilled')
            .map(result => result.value);

        const padding = 10;

        // 计算总高和最大宽
        let totalHeight = padding;
        let maxWidth = 0;

        images.forEach(img => {
            totalHeight += img.height + padding;
            maxWidth = Math.max(maxWidth, img.width);
        });

        const canvas = createCanvas(maxWidth + padding * 2, totalHeight);
        const ctx = canvas.getContext('2d');

        let y = padding;
        images.forEach(img => {
            ctx.drawImage(img, padding, y);
            y += img.height + padding;
        });

        return canvas.toDataURL(); // 返回合并后的 Base64 图像
    },
    saveBase64Image(base64Str) {
        // 确保 /files 文件夹存在
        const dirPath = path.join(__dirname, '../../files');
        if (!fs.existsSync(dirPath)) {
            fs.mkdirSync(dirPath, { recursive: true });
        }

        // 解析 base64 字符串
        const matches = base64Str.match(/^data:(.+);base64,(.+)$/);
        if (!matches || matches.length !== 3) {
            throw new Error('无效的 base64 字符串');
        }

        const mimeType = matches[1]; // e.g., 'image/png'
        const base64Data = matches[2];
        const buffer = Buffer.from(base64Data, 'base64');

        // 生成随机文件名
        const randomName = crypto.randomBytes(16).toString('hex');
        const extension = mimeType.split('/')[1]; // e.g., 'png'
        const filename = `${randomName}.${extension}`;
        const filePath = path.join(dirPath, filename);

        // 写入文件
        fs.writeFileSync(filePath, buffer);
        console.log(`图片已保存到 ${filePath}`);

        // 只返回文件名
        return filename;
    },
    async parseDocument(filePath) {
        const extname = path.extname(filePath).toLowerCase();
        let content = '';
        let images = [];

        if (extname === '.pdf') {
            // 解析 PDF 文件
            const pdfExtract = new PDFExtract();
            const options = {};
            const data = await new Promise((resolve, reject) => {
                pdfExtract.extract(filePath, options, (err, data) => {
                    if (err) reject(err);
                    else resolve(data);
                });
            });

            content = data.text;
            images = data.images.map((image, index) => {
                const imageBuffer = Buffer.from(image.data, 'base64');
                const imagePath = path.join(__dirname, `image-${index}.jpg`);
                fs.writeFileSync(imagePath, imageBuffer);
                return imagePath;
            });
        } else if (extname === '.docx') {
            // 解析 DOCX 文件

            const extractor = (await officeTextExtractor()).getTextExtractor();
            const text = await extractor.extractText({ input: filePath, type: 'file' });
            content = text;
            // 处理图片提取逻辑
            // 注意：office-text-extractor 目前不支持提取图片，您可能需要使用其他库，如 mammoth 或 docxtemplater
        } else {
            throw new Error('不支持的文件格式');
        }

        return { content, images };
    },
    async extractDocStream(filePath) {
        const ext = path.extname(filePath).toLowerCase();
        const result = [];

        if (ext === '.docx') {
            // 使用 Mammoth 将 DOCX 转为 HTML（包含内联 Base64 图像）
            const { value: html } = await mammoth.convertToHtml(
                { path: filePath },
                {
                    convertImage: mammoth.images.imgElement(image =>
                        image.read("base64").then(imageBuffer => ({
                            src: `data:${image.contentType};base64,${imageBuffer}`
                        }))
                    )
                }
            );
            // 解析 HTML，按顺序提取文本和图片
            const $ = cheerio.load(html);
            $('body').children().each((i, elem) => {
                const node = $(elem);
                // 将段落/标题中的文本和图片 拆开按序输出
                let textBuf = '';
                node.contents().each((j, child) => {
                    if (child.type === 'text') {
                        textBuf += $(child).text();
                    } else if (child.tagName === 'img') {
                        // 有未输出的文字时先存为 text 元素
                        if (textBuf.trim()) {
                            result.push({ type: 'text', value: textBuf.trim() });
                            textBuf = '';
                        }
                        // 提取图片的 Base64 内容
                        const dataUrl = $(child).attr('src');
                        const base64 = dataUrl.split(',')[1] || '';
                        result.push({ type: 'image', value: base64 });
                    }
                });
                if (textBuf.trim()) {
                    result.push({ type: 'text', value: textBuf.trim() });
                }
            });
        }
        else if (ext === '.pdf') {
            const dataBuffer = fs.readFileSync(filePath);
            // 提取 PDF 文本（pdf-parse 输出整页文本）
            const pdfData = await pdfParse(dataBuffer);
            if (pdfData.text && pdfData.text.trim()) {
                result.push({ type: 'text', value: pdfData.text.trim() });
            }
            // 提取 PDF 嵌入图片（使用 pdf-extract-image）
            const images = await extractImagesFromPdf(dataBuffer);
            console.log(images)
            for (const imgBuf of images) {
                result.push({ type: 'image', value: imgBuf.toString('base64') });
            }
        }
        return result;
    },
    saveStringToRandomFile(content, ext = 'txt') {
        // 确保 files 目录存在
        const dirPath = path.resolve(__dirname, '../../files');
        if (!fs.existsSync(dirPath)) {
            fs.mkdirSync(dirPath, { recursive: true });
        }

        // 生成唯一文件名
        const timestamp = Date.now();
        const randomHex = crypto.randomBytes(4).toString('hex');
        const filename = `${timestamp}-${randomHex}.${ext}`;

        // 文件完整路径
        const filePath = path.join(dirPath, filename);

        // 写入内容
        fs.writeFileSync(filePath, content, 'utf8');

        return filename; // 返回文件路径
    },
    async createLabeledImage(base64Images) {
        const canvas = createCanvas(800, 600); // 创建一个画布
        const ctx = canvas.getContext('2d');

        let yOffset = 0; // 图片的垂直偏移量

        for (let i = 0; i < base64Images.length; i++) {
            const base64Image = base64Images[i];
            const imgBuffer = Buffer.from(base64Image, 'base64'); // 将 Base64 字符串转换为 Buffer 对象

            const image = await loadImage(imgBuffer); // 加载图片

            // 绘制标签
            ctx.font = '20px Arial';
            ctx.fillText(`${i + 1}:`, 10, yOffset + 20);

            // 绘制图片
            ctx.drawImage(image, 0, yOffset + 30);

            yOffset += image.height + 30; // 更新偏移量
        }

        // 返回拼接后的图片的 Base64 编码
        return canvas.toDataURL('image/png');
    },
    selectValueFromArray(arr, field) {
        let re = []
        arr.forEach(item => {
            re.push(item[field])
        })
        return re
    },
    async saveBufferImage(imageBuffer) {
        const filesDir = path.resolve(__dirname, '../../files');

        // 确保文件夹存在
        if (!fs.existsSync(filesDir)) {
            fs.mkdirSync(filesDir, { recursive: true });
        }

        // 生成唯一的文件名
        const uniqueName = crypto.randomBytes(16).toString('hex');
        const filePath = path.join(filesDir, `${uniqueName}.png`);

        // 写入文件
        await fs.promises.writeFile(filePath, imageBuffer);

        return filePath;
    },
    formatTimeDifference(date1, date2) {
        date1 = new Date(date1);
        date2 = new Date(date2);
        let diff = Math.abs(date1 - date2); // 计算时间差（毫秒）

        const milliseconds = diff % 1000;
        diff = Math.floor(diff / 1000); // 转换为秒
        const seconds = diff % 60;
        diff = Math.floor(diff / 60); // 转换为分钟
        const minutes = diff % 60;
        diff = Math.floor(diff / 60); // 转换为小时
        const hours = diff % 24;
        diff = Math.floor(diff / 24); // 转换为天
        const days = diff % 30;
        diff = Math.floor(diff / 30); // 转换为月
        const months = diff % 12;
        const years = Math.floor(diff / 12); // 转换为年

        // 构建一个数组，按年、月、天、小时、分钟、秒、毫秒的顺序
        const timeUnits = [
            { value: years, unit: '年' },
            { value: months, unit: '个月' },
            { value: days, unit: '天' },
            { value: hours, unit: '小时' },
            { value: minutes, unit: '分钟' },
            { value: seconds, unit: '秒' },
            { value: milliseconds, unit: '毫秒' }
        ];

        // 过滤掉值为0的单位
        const result = timeUnits
            .filter(item => item.value !== 0)
            .map(item => `${item.value} ${item.unit}`)
            .join(' ');

        return result;
    },
    extractJsonFromString(str) {
        try {
            // 移除 markdown 包裹语法，如 ```json、``` 等
            const cleaned = str
                .replace(/```json|```js|```ts|```/g, '')
                .replace(/^\s+|\s+$/g, '') // 去除首尾空白字符

            // 定位最外层 JSON 起始与结束（支持对象 {} 或数组 []）
            const startIndex = Math.min(
                cleaned.indexOf('{') === -1 ? Infinity : cleaned.indexOf('{'),
                cleaned.indexOf('[') === -1 ? Infinity : cleaned.indexOf('[')
            );

            const endIndex = Math.max(
                cleaned.lastIndexOf('}'),
                cleaned.lastIndexOf(']')
            );

            if (startIndex === Infinity || endIndex === -1 || endIndex <= startIndex) {
                console.error('❌ 无法定位完整 JSON 结构');
                return null;
            }

            const jsonText = cleaned.slice(startIndex, endIndex + 1);

            return JSON.parse(jsonText);
        } catch (err) {
            console.error('❌ JSON解析失败:', err.message);
            return null;
        }
    },
    isAnswerCorrect(userAnswer, answer) {
        userAnswer = userAnswer.split('')
        answer = answer.split('')
        if (userAnswer.length > answer.length) return 0
        for (let i = 0; i < userAnswer.length; i++)if (answer.indexOf(userAnswer[i]) + 1 === 0) {
            return 0
        }
        if (userAnswer.length === answer.length) return 1
        if (userAnswer.length === 0) return 0
        return 2
    },
    async computeEssayQuestionScore(questionCacheId) {
        let currentQuestionCache = await this.methods.exam().getQuestionCacheById(questionCacheId)
        let ai_answer = await tool.openai.send(`请你给下面的论述题打一个分数，满分为100分，分数为整数，直接返回一个整数数字，不要带有其他字符。题干：${currentQuestionCache.questionInfo.stems}，
                        答案：${currentQuestionCache.questionInfo.answer}，
                        解析：${currentQuestionCache.questionInfo.analysis}，
                        用户提交的答案：${currentQuestionCache.answer.text}`)
        ai_answer = parseInt(ai_answer)
        return ai_answer
    },
    async getAnswerObject(questionId, selectedOptions) {
        let selectedOptions_c = selectedOptions
        selectedOptions = selectedOptions.split('')
        let questionItem = await this.methods.question().getSingle(questionId)
        let re = {
            options: selectedOptions_c,
            text: ''
        }
        for (let i = 0; i < selectedOptions.length; i++) {
            re.text += `${questionItem[`option${selectedOptions[i]}`]};`
        }
        return re
    },
    async computeNotEassyQuestionScore(questionId, userAnswer) {
        let questionItem = await this.methods.question().getSingle(questionId)
        if (!questionItem) throw '问题不存在'
        if (questionItem.type !== '选择') throw '不是选择题'
        let re = this.isAnswerCorrect(userAnswer, questionItem.answer)
        if (re === 0) return 0
        if (re === 1) return 100
        return 50
    },
    getRandomInt(max) {
        return parseInt(Math.random() * 100000) % (max + 1)
    },
    async parseFile(path) {
        const result = await parseOfficeAsync(path, {
            newline: '\n',
            // 可配置例如 includeImages: true, 或自定义段落划分
        });
        return result
    },
}

function loadImageFromBase64(base64) {
    return loadImage(base64);
}

const check = {
    isArray(e) {
        return Array.isArray(e)
    },
    isInt(e) {
        return (typeof (e) == 'number') && (parseInt(e) == e)
    },
    isNumber(e) {
        return (typeof (e) == 'number')
    },
    isString(e) {
        return typeof (e) == 'string'
    },
    throwError(reason) {
        throw new Error(reason)
    }
}


// ---- DOCX ----
async function extractFromDocx(file) {
    const zip = await JSZip.loadAsync(file);
    const folder = zip.folder('word/media');
    if (!folder) return [];
    const images = [];
    await Promise.all(Object.values(folder.files).map(async entry => {
        const data = await entry.async('base64');
        const ext = entry.name.split('.').pop();
        images.push(`data:image/${ext};base64,${data}`);
    }));
    return images;
}

// ---- DOC ----
async function extractFromDoc(file) {
    const arrayBuffer = await file.arrayBuffer();
    const { value: html } = await mammoth.convertToHtml({ arrayBuffer });
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    return Array.from(doc.querySelectorAll('img')).map(img => img.src);
}

// ---- PDF (.pdf) ----
async function extractFromPdf(file) {
    // 1. 加载 PDF 文档
    const arrayBuffer = await file.arrayBuffer();
    const pdfDoc = await PDFDocument.load(arrayBuffer);
    const imagesBase64 = [];

    // 2. 遍历每一页，提取图片对象
    for (const page of pdfDoc.getPages()) {
        const objs = page.node.context.indirectObjects;
        for (const [ref, obj] of objs) {
            // 筛选出 Image XObject
            if (obj.dict && obj.dict.get('Subtype')?.name === 'Image') {
                const raw = obj.contents; // Uint8Array
                // 根据文件头判定 MIME 类型
                const mime = (await getMimeType(raw)) || 'image/jpeg';
                const b64 = Buffer.from(raw).toString('base64');
                imagesBase64.push(`data:${mime};base64,${b64}`);
            }
        }
    }
    return imagesBase64;
}
